﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Common;
using Lucene.Net.Analysis.Standard;
using System.Threading;
using Lucene.Net.Analysis;
using Lucene.Net.Highlight;

namespace LuceneManager
{
    
    public class LuceneReader
    {
        class SearchArguments
        {
            public Dictionary<int, Document> docs;
            public int searcherNumber;
            public Query query;
        }

        IndexNode[] _indexNodes;
        List<IndexSearcher> _indexSearchers;
        Thread[] _searchThreads;
        object docLock = new object();

        public LuceneReader(IndexNode[] indexNodes)
        {
            _indexNodes = indexNodes;
            InitSearchers();
        }

        private void InitSearchers()
        {
            if (null == _indexSearchers)
            {
                _indexSearchers = new List<IndexSearcher>();
                for (int i = 0; i < _indexNodes.Length; i++)
                {
                    if (IndexExist(_indexNodes[i].IndexPath))
                        _indexSearchers.Add(new IndexSearcher(_indexNodes[i].IndexPath));
                }
            }
            if (null == _searchThreads)
                _searchThreads = new Thread[_indexNodes.Length];
            for (int i = 0; i < _searchThreads.Length; i++)
            {
                //_searchThreads[i] = new Thread(new ParameterizedThreadStart(SearchNode)); 
            }
        }

        public List<Document> DistributedSearch(Query query, int fromDoc, int toDoc, out int totalDocNumber)
        {
            MultiSearcher searcher = new MultiSearcher(_indexSearchers.ToArray());
            List<Document> res = new List<Document>();
            Hits docs = searcher.Search(query);
            if (fromDoc < 0)
                fromDoc = 0;
            if (toDoc == -1 || toDoc >= docs.Length())
                toDoc = docs.Length() - 1;
            for (int i = fromDoc; i < toDoc; i++)
            {
                
                res.Add(LuceneDocumentToKnowfirstDocument(docs.Doc(i)));
            }
            totalDocNumber = docs.Length();
            return res;
        }
/*
        /// <summary>
        /// Does a distributed search across all indexes, merges results without duplicates.
        /// (Duplicate = same Url + Comment Number)
        /// </summary>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public List<Document> DistributedSearch(Query query)
        {
            Dictionary<int, Document> docs = new Dictionary<int,Document>();
            for (int i = 0; i < _indexNodes.Length; i++)
            { 
                SearchArguments sa = new SearchArguments();
                sa.docs = docs;
                sa.searcherNumber = i;
                sa.query = query;
                _searchThreads[i].Start((object)sa);
            }
            for (int i = 0; i < _indexNodes.Length; i++)
            {
                _searchThreads[i].Join();
            }

            List<Document> res = new List<Document>();
            foreach (KeyValuePair<int, Document> kvp in docs)
            {
                res.Add((Document)kvp.Value);
            }
            return res;
        }

        private void SearchNode(object searchArguments)
        {
 
            SearchArguments sa = (SearchArguments)searchArguments;
            Dictionary<int, Document> docs = sa.docs;
            Lucene.Net.Search.Hits hits = _indexSearchers[sa.searcherNumber].Search(sa.query);
            for (int i = 0; i < hits.Length(); i++)
            {
                Document doc = LuceneDocumentToKnowfirstDocument(hits.Doc(i));
                doc.Score = hits.Score(0);
                lock (docLock)
                    if (!docs.ContainsKey(doc.GetHashCode()))
                        docs.Add(doc.GetHashCode(), doc);
            }
        }
*/

        public Query GetQueryObjectForDocumentsMatchingQuery(string QueryString, 
            Analyzer analyzer)
        {   

            string queryString = QueryString.Replace("\"", "\\\"");
            string finalQuery = "(Title:\"" + queryString
              + "\"~1^20) OR (Summary:\"" +
            queryString + "\"~1^10) OR (Text:\"" + queryString + "\"~1)";
            Lucene.Net.QueryParsers.QueryParser queryParser = new
            Lucene.Net.QueryParsers.QueryParser("Text", analyzer);
            /*
            //parse the query string into a Query object
            Lucene.Net.Search.Query titleQuery = queryParser.Parse("(Title:\"" + queryString 
                + "\")");
            ((PhraseQuery)titleQuery).SetSlop(5);
            titleQuery.SetBoost(20);

            Lucene.Net.Search.Query summaryQuery = queryParser.Parse("(Summary:\"" + queryString
               + "\")");
            ((PhraseQuery)summaryQuery).SetSlop(5);
            summaryQuery.SetBoost(10);

            Lucene.Net.Search.Query textQuery = queryParser.Parse("(Text:\"" + queryString
               + "\")");
            ((PhraseQuery)textQuery).SetSlop(5);
            textQuery.SetBoost(0);

            BooleanQuery query = new BooleanQuery();
            query.Add(titleQuery, BooleanClause.Occur.SHOULD);
            query.Add(summaryQuery, BooleanClause.Occur.SHOULD);
            query.Add(textQuery, BooleanClause.Occur.SHOULD);
            */
            Query query = queryParser.Parse(finalQuery);





            return query;

        }
        public List<Document> GetDocumentsMatchingQuery(string queryString, 
            int fromDoc, int toDoc, out int totalNumDocs)
        {
            //queryString = queryString.Replace("\"", "\\\"");
            // string finalQuery = "(Title:\"" + queryString + "\"^20) OR (Summary:\"" +
            //  queryString + "\"^10) OR (Text:\"" + queryString + "\")";
            Lucene.Net.Search.Query query =
                  GetQueryObjectForDocumentsMatchingQuery(queryString,
                  new StandardAnalyzer());
            return DistributedSearch(query, fromDoc, toDoc, out totalNumDocs);
        }

        public Document GetDocumentByUrlCommentNumber(string url, long commentNumber)
        {

            BooleanQuery bq = new BooleanQuery();
            TermQuery uri = new TermQuery(new Term("Uri", url));
            TermQuery commentNum = new TermQuery(new Term("CommentNumber",
                commentNumber.ToString()));
            bq.Add(uri, BooleanClause.Occur.MUST);
            bq.Add(commentNum, BooleanClause.Occur.MUST);
            //parse the query string into a Query object
            int temp;
            List<Document> docs = DistributedSearch(bq, 0, 0, out temp);
           if (docs.Count > 0)
           {

               return docs[0];
           }
           else return null;
        }

        public Document GetDocumentById(long id)
        {

            BooleanQuery bq = new BooleanQuery();
            TermQuery ID = new TermQuery(new Term("ID", id.ToString()));
            bq.Add(ID, BooleanClause.Occur.MUST);
            //parse the query string into a Query object
            int temp;
            List<Document> docs = DistributedSearch(bq, 0, 0, out temp);
            if (docs.Count > 0)
            {

                return docs[0];
            }
            else return null;
        }



        public static bool IndexExist(string path)
        {
            return IndexReader.IndexExists(path);
        }

     

        public static Document LuceneDocumentToKnowfirstDocument(Lucene.Net.Documents.Document doc)
        {
            Document kdoc = new Document(Int32.Parse(doc.GetField("ID").StringValue()),
                DateTime.Parse(doc.GetField("DateTime").StringValue()),
                new Uri(doc.GetField("Uri").StringValue()),
                long.Parse(doc.GetField("CommentNumber").StringValue()),
                Int32.Parse(doc.GetField("TalkbackNumber").StringValue()),
                (Document.Type)Enum.Parse(typeof(Document.Type),
                doc.GetField("DocumentType").StringValue()), 0,
                doc.GetField("SiteName").StringValue());
            if (doc.GetField("SentimentID") != null)
                kdoc.SentimentId = Int32.Parse(doc.GetField("SentimentID").StringValue());
            else
                kdoc.SentimentId = 0;
            if (doc.GetField("DownloadDateTime") != null)
                kdoc.DownloadDateTime = DateTime.Parse(doc.GetField("DownloadDateTime").StringValue());
            else
                kdoc.DownloadDateTime = DateTime.MinValue;
            kdoc.Fields[(int)Document.Section.Author] = new Field("", Document.Section.Author);
            kdoc.Fields[(int)Document.Section.Title] = new Field(doc.GetField("Title").StringValue(), Document.Section.Title);
            kdoc.Fields[(int)Document.Section.Summary] = new Field(doc.GetField("Summary").StringValue(), Document.Section.Summary);
            kdoc.Fields[(int)Document.Section.Text] = new Field(doc.GetField("Text").StringValue(), Document.Section.Text);
            return kdoc;
        }

        public string[] GetHTMLExcerpts(List<string> queries, Document doc, string prefixFilePath)
        {
            List<string> res = new List<string>();
            List<string> rawFragList = new List<string>();
            foreach (string query in queries)
            {
                Highlighter hl = new Highlighter(new SimpleHTMLFormatter(),
                    new QueryScorer(GetQueryObjectForDocumentsMatchingQuery(query,
                        new SimpleAnalyzer()),
                         new Common.HebrewStemmer(prefixFilePath)));
                
                string fragment = hl.GetBestFragment(new SimpleAnalyzer(), "Text", doc.Text);
                if (null != fragment)
                {
                    string rawFrag = fragment.Replace("<B>", "");
                    rawFrag = rawFrag.Replace("</B>", "");
                    if (!rawFragList.Contains(rawFrag))
                    {
                        rawFragList.Add(rawFrag);
                        res.Add("..." + fragment + "...");
                    }
                    
                }
            }
            return res.ToArray();
        }

    }
}
